/*
 * Copyright 2010 Interactive Media Management
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package dk.i2m.converge.ejb.services;

import dk.i2m.converge.core.ConfigurationKey;
import dk.i2m.converge.core.newswire.NewswireDecoderException;
import dk.i2m.converge.domain.SystemTimer;
import dk.i2m.converge.core.newswire.NewswireItem;
import dk.i2m.converge.core.newswire.NewswireService;
import dk.i2m.converge.core.security.UserAccount;
import dk.i2m.converge.ejb.facades.UserFacadeLocal;
import dk.i2m.converge.core.plugin.PluginManager;
import dk.i2m.converge.core.plugin.NewswireDecoder;
import dk.i2m.converge.ejb.facades.SystemFacadeLocal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Resource;
import javax.ejb.EJB;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;
import javax.ejb.Timeout;
import javax.ejb.Timer;
import javax.ejb.TimerService;

/**
 * Stateless enterprise bean providing access to the external newswire services.
 *
 * @author Allan Lykke Christensen
 */
@Stateless
public class NewswireServiceBean implements NewswireServiceLocal {

    private static final Logger logger = Logger.getLogger(NewswireServiceBean.class.getName());

    private static final String TIMER_RELOAD = "Newswire Reload Timer";

    @Resource private TimerService timerService;

    @EJB private ConfigurationServiceLocal cfgService;

    @EJB private DaoServiceLocal daoService;

    @EJB private UserFacadeLocal userFacade;

    @EJB private SystemFacadeLocal systemFacade;

    @Resource private SessionContext ctx;

    /**
     * Creates a new instance of {@link NewswireBean}.
     */
    public NewswireServiceBean() {
    }

    /** {@inheritDoc} */
    @Override
    public void startTimers() {
        logger.log(Level.FINE, "Starting timers");
        for (Timer t : (Collection<Timer>) timerService.getTimers()) {
            if (t.getInfo().equals(TIMER_RELOAD)) {
                logger.log(Level.INFO, "Timer is already active");
                return;
            }
        }

        Calendar now = Calendar.getInstance();
        now.set(Calendar.HOUR_OF_DAY, 0);
        now.set(Calendar.MINUTE, 0);
        now.set(Calendar.SECOND, 0);
        now.roll(Calendar.DATE, false);

        String strReloadInterval = cfgService.getString(ConfigurationKey.NEWSWIRE_INTERVAL);
        Long reloadInterval = Long.valueOf(strReloadInterval) * 60L * 1000L;
        logger.log(Level.INFO, "Starting timer [" + TIMER_RELOAD + "] at {0}" + " " + " repeat every: {1} ms / {2} hrs / {3} mins ", new Object[]{now.getTime().toString(), reloadInterval, reloadInterval / 3600000L, reloadInterval / 60000L});
        timerService.createTimer(now.getTime(), reloadInterval, TIMER_RELOAD);
    }

    /** {@inheritDoc} */
    @Override
    public void stopTimers() {
        logger.info("Stopping timers");
        for (Timer timer : (Collection<Timer>) timerService.getTimers()) {
            if (timer.getInfo() instanceof String) {
                logger.log(Level.INFO, "Stopping timer: {0}", timer.getInfo());
                timer.cancel();
            }
        }
    }

    /** {@inheritDoc} */
    @Override
    public List<SystemTimer> getTimers() {
        List<SystemTimer> timers = new ArrayList<SystemTimer>();
        for (Timer timer : (Collection<Timer>) timerService.getTimers()) {
            SystemTimer sysTimer = new SystemTimer();
            sysTimer.setNextTimeout(timer.getNextTimeout());
            sysTimer.setTimeRemaining(timer.getTimeRemaining());
            sysTimer.setName((String) timer.getInfo());
            timers.add(sysTimer);
        }
        return timers;
    }

    /** {@inheritDoc} */
    @Override
    public List<NewswireItem> getTodaysNews() {
        try {
            UserAccount user = userFacade.findById(ctx.getCallerPrincipal().getName());
            Calendar start = Calendar.getInstance();
            start.set(Calendar.HOUR_OF_DAY, 0);
            start.set(Calendar.MINUTE, 0);
            start.set(Calendar.SECOND, 0);

            Calendar end = Calendar.getInstance();
            end.set(Calendar.HOUR_OF_DAY, 23);
            end.set(Calendar.MINUTE, 59);
            end.set(Calendar.SECOND, 59);

            Map<String, Object> params = QueryBuilder.with("start", start).and("end", end).and("user", user).parameters();
            return daoService.findWithNamedQuery(NewswireItem.FIND_BY_DATE_USER, params);
        } catch (DataNotFoundException ex) {
            return Collections.EMPTY_LIST;
        }
    }

    /** {@inheritDoc} */
    @Override
    public List<NewswireItem> getNews() {
        try {
            UserAccount user = userFacade.findById(ctx.getCallerPrincipal().getName());
            Map<String, Object> params = QueryBuilder.with("user", user).parameters();
            return daoService.findWithNamedQuery(NewswireItem.FIND_BY_USER, params);
        } catch (DataNotFoundException ex) {
            return Collections.EMPTY_LIST;
        }
    }

    /** {@inheritDoc} */
    @Override
    public List<NewswireItem> getNews(Long newswireServiceId) {
        try {
            NewswireService service = daoService.findById(NewswireService.class, newswireServiceId);
            Map<String, Object> params = QueryBuilder.with("newswireService", service).parameters();
            return daoService.findWithNamedQuery(NewswireItem.FIND_BY_SERVICE, params);
        } catch (DataNotFoundException ex) {
            return Collections.EMPTY_LIST;
        }
    }

    /** {@inheritDoc} */
    @Override
    public List<NewswireService> getNewswireServices() {
        return daoService.findAll(NewswireService.class);
    }

    /** {@inheritDoc} */
    @Override
    public List<NewswireService> getNewswireServicesWithSubscribersAndItems() {
        List<NewswireService> services = getNewswireServices();

        for (NewswireService service : services) {
            try {
                Long subscribers = daoService.findObjectWithNamedQuery(Long.class, NewswireService.COUNT_SUBSCRIBERS, QueryBuilder.with("id", service.getId()).parameters());
                Long items = daoService.findObjectWithNamedQuery(Long.class, NewswireService.COUNT_ITEMS, QueryBuilder.with("id", service.getId()).parameters());
                service.setNumberOfSubscribers(subscribers);
                service.setNumberOfItems(items);

            } catch (Exception ex) {
                logger.log(Level.WARNING, "Unknown response from query.", ex);
            }
        }

        return services;
    }

    /** {@inheritDoc} */
    @Override
    public NewswireService findById(Long id) throws DataNotFoundException {
        return daoService.findById(NewswireService.class, id);
    }

    /** {@inheritDoc} */
    @Override
    public NewswireService create(NewswireService newsFeed) {
        return daoService.create(newsFeed);
    }

    /** {@inheritDoc} */
    @Override
    public void update(NewswireService newsFeed) {
        daoService.update(newsFeed);
    }

    /** {@inheritDoc} */
    @Override
    public void delete(NewswireService newsFeed) {
        daoService.delete(NewswireService.class, newsFeed.getId());
    }

    /** {@inheritDoc} */
    @Override
    public void delete(Long id) throws DataNotFoundException {
        daoService.delete(NewswireService.class, id);
    }

    /** {@inheritDoc} */
    @Override
    public int downloadFeeds() {
        loadFeeds(null);
        return getNews().size();
    }

    /** {@inheritDoc} */
    @Override
    public int downloadNewswireService(Long newswireServiceId) {
        try {
            NewswireService service = daoService.findById(NewswireService.class, newswireServiceId);
            try {
                List<NewswireItem> downloaded = fetch(service);
                service.setLastFetch(Calendar.getInstance());
                return downloaded.size();
            } catch (NewswireDecoderException ex) {
                logger.log(Level.SEVERE, "Could not decode newswire. {1}", ex.getMessage());
                logger.log(Level.FINE, "", ex);
                return 0;
            }
        } catch (DataNotFoundException ex) {
            logger.log(Level.WARNING, "Invalid NewswireService ({0}) requested", newswireServiceId);
            return 0;
        }
    }

    /**
     * Loads all the news feeds into the newswire cache.
     *
     * @param timer
     *          Timer that initiated the feed reloading
     */
    @Timeout
    public void loadFeeds(Timer timer) {
        logger.info("Timer: " + TIMER_RELOAD);

        try {
            // Get all feeds
            List<NewswireService> services = daoService.findAll(NewswireService.class);

            // Load newswire items from each feed
            for (NewswireService service : services) {
                try {
                    fetch(service);
                } catch (NewswireDecoderException ex) {
                    logger.log(Level.SEVERE, "Could not decode newswire. {1}", ex.getMessage());
                    logger.log(Level.FINE, "", ex);
                }
                service.setLastFetch(Calendar.getInstance());
            }
        } catch (Throwable t) {
            logger.log(Level.SEVERE, t.getMessage(), t);
        }
    }

    /** {@inheritDoc} */
    @Override
    public List<NewswireItem> fetch(NewswireService service) throws NewswireDecoderException {
        logger.log(Level.FINE, "Starting newswire service {0}", service.getSource());
        NewswireDecoder decoder = service.getDecoder();

        return decoder.decode(systemFacade.getPluginContext(), service);
    }

    /** {@inheritDoc} */
    @Override
    public List<NewswireItem> search(String search) {
        try {
            UserAccount user = userFacade.findById(ctx.getCallerPrincipal().getName());
            Map<String, Object> params = QueryBuilder.with("user", user).and("keyword", "%" + search + "%").parameters();
            return daoService.findWithNamedQuery(NewswireItem.SEARCH, params);
        } catch (DataNotFoundException ex) {
            return Collections.EMPTY_LIST;
        }
    }

    /** {@inheritDoc} */
    @Override
    public int emptyNewswireService(Long newswireServiceId) {
        int affectedRecords = 0;
        try {
            NewswireService ns = daoService.findById(NewswireService.class, newswireServiceId);
            return daoService.executeQuery(NewswireItem.DELETE_BY_SERVICE, QueryBuilder.with("newswireService", ns));
        } catch (DataNotFoundException ex) {
            return affectedRecords;
        }
    }

    /** {@inheritDoc} */
    @Override
    public List<NewswireItem> findByExternalId(String externalId) {
        Map<String, Object> params = QueryBuilder.with("externalId", externalId).parameters();
        return daoService.findWithNamedQuery(NewswireItem.FIND_BY_EXTERNAL_ID, params);
    }

    /** {@inheritDoc} */
    @Override
    public NewswireItem create(NewswireItem item) {
        return daoService.create(item);
    }

    /** {@inheritDoc} */
    @Override
    public Map<String, NewswireDecoder> getNewswireDecoders() {
        return PluginManager.getInstance().getNewswireDecoders();
    }
}
